package xj.toolkit.jmx.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xj.toolkit.jmx.MBeanRegisterSupport;
import xj.toolkit.util.ExceptionUtils;

public class DefaultMBeanServerConnectionSource implements
		MBeanServerConnectionSource, MBSCSourceMXBean {
	private static final Logger LOG = LoggerFactory
			.getLogger(DefaultMBeanServerConnectionSource.class);

	public void setObjectName(String objectName) {
		this._sourceRegister.unregisterAllMBeans();
		if (null != objectName) {
			this._sourceRegister.registerMBean(objectName, this);
		}
	}

	public void clear() {
		while (!this._jmxcs.isEmpty()) {
			Map.Entry<String, JMXConnector> entry = (Map.Entry) this._jmxcs
					.entrySet().iterator().next();

			tryRemoveAndClosejmxc((JMXConnector) entry.getValue(),
					(String) entry.getKey());
		}
	}

	public MBeanServerConnection getMBeanServerConnection(String jmxurl) {
		if (null == jmxurl) {
			LOG.warn("can't gen MBeanServerConnection for null jmxurl");
			return null;
		}
		JMXConnector jmxc = url2jmxc(jmxurl);
		if (null == jmxc) {
			LOG.error("can't gen MBeanServerConnection for url {}", jmxurl);
			return null;
		}
		try {
			return jmxc.getMBeanServerConnection();
		} catch (Exception e) {
			LOG.warn(
					"exception when gen MBeanServerConnection {}, try to re-connect to MBeanServer {}",
					jmxurl, jmxurl);

			tryRemoveAndClosejmxc(jmxc, jmxurl);
		}
		return getMBeanServerConnection(jmxurl);
	}

	private void tryRemoveAndClosejmxc(JMXConnector jmxc, String jmxurl) {
		this._jmxcs.remove(jmxurl, jmxc);
		try {
			jmxc.close();
		} catch (Exception e) {
			LOG.warn("exception when close jmxc, detail: {}",
					ExceptionUtils.exception2detail(e));
		}
	}

	private JMXConnector url2jmxc(final String jmxurl) {
		JMXConnector jmxc = (JMXConnector) this._jmxcs.get(jmxurl);
		if (null == jmxc) {
			try {
				jmxc = JMXConnectorFactory.connect(new JMXServiceURL(jmxurl),
						null);
				if (null == jmxc) {
					LOG.error("JMXConnectorFactory.connect for {} return null",
							jmxurl);
					return null;
				}
			} catch (Exception e) {
				LOG.error(
						"failed to connect to MBeanServer for url {}, detail: {}",
						jmxurl, ExceptionUtils.exception2detail(e));

				return null;
			}
			JMXConnector oldJmxc = (JMXConnector) this._jmxcs.putIfAbsent(
					jmxurl, jmxc);
			if (null != oldJmxc) {
				try {
					jmxc.close();
				} catch (Exception e) {
					LOG.warn("exception when close redundant jmxc, detail: {}",
							ExceptionUtils.exception2detail(e));
				}
				jmxc = oldJmxc;
			} else {
				jmxc.addConnectionNotificationListener(
						new NotificationListener() {
							public void handleNotification(
									Notification notification, Object handback) {
								JMXConnectionNotification connectionNotif = (JMXConnectionNotification) notification;
								if (("jmx.remote.connection.closed"
										.equals(connectionNotif.getType()))
										|| ("jmx.remote.connection.notifs.lost"
												.equals(connectionNotif
														.getType()))) {
									DefaultMBeanServerConnectionSource.LOG
											.info("handleNotification for {}'s {}, try remove and close jmxc",
													jmxurl,
													connectionNotif.getType());

									DefaultMBeanServerConnectionSource.this
											.tryRemoveAndClosejmxc(
													(JMXConnector) handback,
													jmxurl);
								}
							}
						}, null, jmxc);
			}
		}
		return jmxc;
	}

	public String[] getAlivedJmxurls() {
		return (String[]) this._jmxcs.keySet().toArray(new String[0]);
	}

	private final MBeanRegisterSupport _sourceRegister = new MBeanRegisterSupport(
			null, null);
	private final ConcurrentMap<String, JMXConnector> _jmxcs = new ConcurrentHashMap();
}
